
package com.alibaba.csp.sentinel.dashboard.repository.metric.InfluxDb;

import com.alibaba.csp.sentinel.dashboard.datasource.entity.MetricEntity;
import com.alibaba.csp.sentinel.dashboard.repository.metric.MetricsRepository;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.nacos.client.naming.utils.CollectionUtils;
import com.influxdb.client.*;

import com.influxdb.client.domain.WritePrecision;

import com.influxdb.query.FluxRecord;
import com.influxdb.query.FluxTable;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Repository;

import java.time.Instant;
import java.time.temporal.TemporalUnit;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static java.time.temporal.ChronoUnit.HOURS;


@Repository("inInfluxdbMetricsRepository")
public class InInfluxdbMetricsRepository implements MetricsRepository<MetricEntity> {

    @Autowired
    private InfluxDbConfig influxDBConfig;

    @Autowired
    public WriteApiBlocking writeApiBlocking;



    @Autowired
    public InfluxDBClient influxDBClient;

    @Autowired
    public QueryApi queryApi;



    @Override
    public synchronized void save(MetricEntity metric) {

        try {
            InfluxdbMetricEntity entity = new InfluxdbMetricEntity();

            BeanUtils.copyProperties(metric, entity, new String[]{"gmtCreate", "gmtModified", "timestamp"});
            entity.setResource(metric.getResource());
            entity.setGmtCreate(metric.getGmtCreate().getTime());
            entity.setGmtModified(metric.getGmtModified().getTime());
            entity.setTimestamp(metric.getTimestamp().getTime());
            entity.setPassQps(metric.getPassQps());
            entity.setSuccessQps(metric.getSuccessQps());
            entity.setBlockQps(metric.getBlockQps());
            entity.setExceptionQps(metric.getExceptionQps());
            entity.setRt(metric.getRt());
            entity.setCount(metric.getCount());
            entity.setResourceCode(metric.getResourceCode());
            entity.setTime(Instant.now());
            writeApiBlocking.writeMeasurement(WritePrecision.MS, entity);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    @Override
    public synchronized void saveAll(Iterable<MetricEntity> metrics) {
        if (metrics == null) {
            return;
        }
        metrics.forEach(this::save);

    }


    @Override
    public synchronized List<MetricEntity> queryByAppAndResourceBetween(String app, String resource, long startTime, long endTime) {
        List<MetricEntity> results = new ArrayList<>();
        if (StringUtil.isBlank(app)) {
            return results;
        }
        String command = String.format("from(bucket:\"%s\") |> range(start: -1h)"
                        + " |> filter(fn: (r) => (r[\"_measurement\"] == \"sentinelInfo\" and r[\"app\"] == \"%s\") and r[\"resource\"] == \"%s\")",
                influxDBConfig.getBucket(),  app, resource);
        QueryApi queryApi = influxDBClient.getQueryApi();
        List<FluxTable> tables = queryApi.query(command);
        for (FluxTable fluxTable : tables) {
            List<FluxRecord> records = fluxTable.getRecords();
            for (FluxRecord fluxRecord : records) {
                MetricEntity metricEntity = MetricEntity.copyOf(fluxRecord);
                results.add(metricEntity);
            }
        }
        return results;
    }

    @Override
    public synchronized List<String> listResourcesOfApp(String app) {
        List<String> results = new ArrayList<>();
        if (StringUtil.isBlank(app)) {
            return results;
        }
        //最近一分钟的指标(实时数据)
        final long minTimeMs = System.currentTimeMillis() - 1000 * 60;
        String command1 = String.format("from(bucket:\"%s\") |> range(start: -1h)\n" +
                "  |> filter(fn: (r) => r[\"_measurement\"] == \"sentinelInfo\")\n" +
                "  |> filter(fn: (r) => r[\"app\"] == \"" + app + "\")",influxDBConfig.getBucket());
        QueryApi queryApi = influxDBClient.getQueryApi();
        List<FluxTable> tables = queryApi.query(command1);
        List<MetricEntity> influxResults = new ArrayList<>();
        for (FluxTable fluxTable : tables) {
            List<FluxRecord> records = fluxTable.getRecords();
            for (FluxRecord fluxRecord : records) {
                MetricEntity metricEntity = MetricEntity.copyOf(fluxRecord);
                influxResults.add(metricEntity);
            }
        }

        try {
            if (CollectionUtils.isEmpty(influxResults)) {
                return results;
            }
            Map<String, MetricEntity> resourceCount = new HashMap<>(32);
            for (MetricEntity metricEntity : influxResults) {
                String resource = metricEntity.getResource();
                if (resourceCount.containsKey(resource)) {
                    MetricEntity oldEntity = resourceCount.get(resource);
                    oldEntity.addPassQps(metricEntity.getPassQps());
                    oldEntity.addRtAndSuccessQps(metricEntity.getRt(), metricEntity.getSuccessQps());
                    oldEntity.addBlockQps(metricEntity.getBlockQps());
                    oldEntity.addExceptionQps(metricEntity.getExceptionQps());
                    oldEntity.addCount(1);
                } else {
                    resourceCount.put(resource, metricEntity);
                }
            }
            //排序
            results = resourceCount.entrySet()
                    .stream()
                    .sorted((o1, o2) -> {
                        MetricEntity e1 = o1.getValue();
                        MetricEntity e2 = o2.getValue();
                        int t = e2.getBlockQps().compareTo(e1.getBlockQps());
                        if (t != 0) {
                            return t;
                        }
                        return e2.getPassQps().compareTo(e1.getPassQps());
                    })
                    .map(Map.Entry::getKey)
                    .collect(Collectors.toList());
        } catch (Exception e) {
            e.printStackTrace();
        }

        return results;
    }
}